//    00001: ld
//    00010: st
//    00011: add
//    00100: sub
//    00101: sll
//    00110: srl
//    00111: and
//    01000: or
//    01001: nor
//    01010: xnor
//    01011: in
//    01100: out
//    01101: halt
//    01110: mov
//    01111: movi
//    10000: ba
//    10001: be
//    10010: bne
//    10011: bg
//    10100: bge
//    10101: bl
//    10110: ble
//    11011: call
//    11111: return

`timescale 1ns / 1ps

// I_WIDTH  : instruction width
// IA_WIDTH : instruction address width
// D_WIDTH  : data width
// PA_WIDTH : port address width
 
module backend#(parameter I_WIDTH = 17, IA_WIDTH = 10, D_WIDTH = 34, PA_WIDTH = 4)
(
    // outputs for debugging
    output [D_WIDTH-1:0] reg1,
    output [D_WIDTH-1:0] reg2, 
    output [D_WIDTH-1:0] reg3, 
    output [D_WIDTH-1:0] reg4, 
    output [D_WIDTH-1:0] reg5, 
    output [D_WIDTH-1:0] reg6, 
    output [D_WIDTH-1:0] reg7, 
    output [D_WIDTH-1:0] reg8,   
    
    input  clk,
    input  reset_i,
    
    // inputs from the fetch unit
    input  [I_WIDTH-1 : 0]  instruction_data_i,
    input  [IA_WIDTH-1 : 0] instruction_addr_i,
    input  instruction_valid_i,    
    
    // input from the fetch unit not used
    input  [I_WIDTH-1 : 0]  load_data_i,
    input  load_data_valid_i,    
    
    // outputs to the fetch unit
    output dequeue_o,
    output restart_o,
    output [IA_WIDTH-1 : 0] restart_addr_o,        
    output branch_o,
    output [I_WIDTH-1 : 0]  store_data_o,	// store %fp in branch
    
    // outputs to the fetch unit not used
    output load_store_valid_o,
    output store_en_o,
    output [IA_WIDTH-1 : 0] load_store_addr_o,
    
    // I/O interface
    input  in_ack_i,
    input  out_ack_i, 
    input  [D_WIDTH-1 : 0]  in_data_i,
    
    output in_req_o,
    output out_req_o,
    output [PA_WIDTH-1 : 0] in_addr_o,
    output [PA_WIDTH-1 : 0] out_addr_o,
    output [D_WIDTH-1 : 0]  out_data_o
); 

////////////////////////////////////////////////////////////////////////
// Requirements:

// - PC register				= 1		DONE
// - register file				= 1		DONE
// - ALU						= 1		DONE
// - data memory				= 1		DONE
// - sign extend		 		= 1		DONE
// - 2-input MUX				= 4		DONE
// - adder						= 2		DONE
// - shift-left 2 				= 1		DONE

////////////////////////////////////////////////////////////////////////
// local params

	localparam REG_BITS = 3;
	localparam IMM_WIDTH = 6;
	localparam LEVEL_WIDTH = 1024;
	
	// localparam LD_OP 	= 5'b00001;
	// localparam ST_OP 	= 5'b00010;
	// localparam ADD_OP	= 5'b00011;
	// localparam SUB_OP	= 5'b00100;
	// localparam SLL_OP 	= 5'b00101;
	// localparam SRL_OP	= 5'b00110;
	// localparam AND_OP	= 5'b00111;
	// localparam OR_OP		= 5'b01000;
	// localparam NOR_OP	= 5'b01001;
	// localparam XNOR_OP	= 5'b01010;
	// localparam IN_OP		= 5'b01011;
	// localparam OUT_OP	= 5'b01100;
	// localparam HALT_OP 	= 5'b01101;
	// localparam MOV_OP	= 5'b01110;
	// localparam MOVI_OP	= 5'b01111;

////////////////////////////////////////////////////////////////////////
// defined instructions

// R-Type Instruction Format -------------------------------------------

// 3: 00011: add
	localparam ADD = 17'b00011_????_????_????;

// 4: 00100: sub
	localparam SUB = 17'b00100_????_????_????;

// 5: 00101: sll
	localparam SLL = 17'b00101_????_????_????;

// 6: 00110: srl
	localparam SRL = 17'b00110_????_????_????;
	
// 7: 00111: and
	localparam AND = 17'b00111_????_????_????;

// 8: 01000: or
	localparam OR = 17'b01000_????_????_????;

// 9: 01001: nor
	localparam NOR = 17'b01001_????_????_????;

// 10: 01010: xnor
	localparam XNOR = 17'b01010_????_????_????;

// 11: 01011: in
	localparam IN = 17'b01011_????_????_????;

// 12: 01100: out
	localparam OUT = 17'b01100_????_????_????;

// I-Type Instruction Format -------------------------------------------

// 1: 00001: ld
	localparam LD = 17'b00001_????_????_????;

// 2: 00010: st
	localparam ST = 17'b00010_????_????_????;

// 14: 01110: mov
	localparam MOV = 17'b01110_????_????_????;
	
// 15: 01111: mov
	localparam MOVI = 17'b01111_????_????_????;

// 16: 10000: ba
	localparam BA = 17'b10000_????_????_????;

// 17: 10001: be
	localparam BE = 17'b10001_????_????_????;

// 18: 10010: bne
	localparam BNE = 17'b10010_????_????_????;

// 19: 10011: bg
	localparam BG = 17'b10011_????_????_????;

// 20: 10100: bge
	localparam BGE = 17'b10100_????_????_????;

// 21: 10101: bl
	localparam BL = 17'b10101_????_????_????;

// 22: 10110: ble
	localparam BLE = 17'b10110_????_????_????;

// 31: 11111: return
	localparam RETURN = 17'b11111_????_????_????;
	
// J-Type Instruction Format -------------------------------------------

// 27: 11011: call
	localparam CALL = 17'b11011_????_????_????;

// Special Case --------------------------------------------------------

// 13: 01101: halt
	localparam HALT = 17'b01101_????_????_????;


////////////////////////////////////////////////////////////////////////
// registers: inputs, outputs
	
	// outputs for debugging
    wire [D_WIDTH-1:0] reg1_r;
    reg [D_WIDTH-1:0] reg2_r;
    reg [D_WIDTH-1:0] reg3_r; 
    reg [D_WIDTH-1:0] reg4_r; 
    reg [D_WIDTH-1:0] reg5_r; 
    reg [D_WIDTH-1:0] reg6_r; 
    reg [D_WIDTH-1:0] reg7_r; 
    reg [D_WIDTH-1:0] reg8_r;  
    
    // inputs from the fetch unit
    
    // outputs to the fetch unit
    reg dequeue_r;
    reg restart_r;
    reg [IA_WIDTH-1 : 0] restart_addr_r;
    reg load_store_valid_r;
    reg store_en_r;
    reg [IA_WIDTH-1 : 0] load_store_addr_r;
    reg [I_WIDTH-1 : 0]  store_data_r;
    reg branch_r;
    
    // I/O interface: input
    reg  [D_WIDTH-1 : 0]  in_data_r;
    reg  in_ack_r;
    reg  out_ack_r;
    
    // I/O interface: output	
    reg in_req_r;
    reg out_req_r;
    reg [PA_WIDTH-1 : 0] in_addr_r;
    reg [PA_WIDTH-1 : 0] out_addr_r;
    reg [D_WIDTH-1 : 0]  out_data_r;
    
 ////////////////////////////////////////////////////////////////////////
// registers: PC

	reg	[D_WIDTH-1:0] pc_r;

////////////////////////////////////////////////////////////////////////
// registers: inputs, outputs for modules

	// ALU
	reg [D_WIDTH-1:0] alu_o_r;
	
	// regfile
	reg	RegWrite;
	reg [D_WIDTH-1:0] regfile_o_1; // aka alu_input_1;
	reg [D_WIDTH-1:0] regfile_o_2;
	reg start_r;
	reg done_r;
	reg [LEVEL_WIDTH-1:0] Level;
	
	// dmem
	reg MemRead;
	reg MemWrite;
	reg dmem_refused_r;
	reg [D_WIDTH-1:0] dmem_o_r;
	
	// MUX: PCSrc
	reg PCSrc;
	
	// MUX: ALUSrc
	reg ALUSrc;
	reg [D_WIDTH-1:0] alu_input_1; // aka regfile_o_1;
	reg [D_WIDTH-1:0] alu_input_2;
   
	// MUX: MemToReg
	reg MemToReg;
	reg	[D_WIDTH-1:0] data_for_write;
	
	// MUX: RegDst
	reg RegDst;
	reg [REG_BITS-1:0] reg_for_write;
	
	// signext
	reg [D_WIDTH-1:0] imm_r;
	
	// shiftleft
	reg [D_WIDTH-1:0] offset_r;
	
	// adder: add_branch
	reg [D_WIDTH-1:0] pc_next_r;
	reg [D_WIDTH-1:0] branch_pc_r;
	reg branch_true_r;
	reg return_true_r;
	reg call_true_r;
	
	// adder: add_pc_next	
	// >> none

////////////////////////////////////////////////////////////////////////
// register: control

	// 1. RegDst
	reg reg_dst_c;
	
	// 2. RegWrite
	reg reg_write_c;
	
	// 3. ALUSrc
	reg alu_src_c;
	
	// 4. MemWrite
	reg mem_write_c;
	
	// 5. MemRead
	reg mem_read_c;
	
	// 6. MemToReg
	reg mem_to_reg_c;
	
	// 7. PCSrc
	reg pc_src_c;
	
	// 8. Level
	reg level_c;

////////////////////////////////////////////////////////////////////////
// assign
 	
	// not care assignment
	assign load_store_valid_r = 1'b0;
	assign load_store_valid_o = load_store_valid_r;
	assign load_store_addr_r = 10'b0;
	assign load_store_addr_o = load_store_addr_r;
	assign store_en_r = 1'b0;
	assign store_en_o = store_en_r;

////////////////////////////////////////////////////////////////////////
// assign: control

	// 1. RegDst
	//    0: I-type instructions: Write to rs [12:10]
	//    1: R-type instructions: Write to rd [6:4]
	//    Note: it doesn't matter for store or branch instructions
	always_comb
	begin
		unique casez (instruction_data_i)
			LD:			reg_dst_c = 1'b0;
			ST: 		reg_dst_c = 1'b0;	// this value doesn't matter
			ADD:		reg_dst_c = 1'b1;
			SUB:		reg_dst_c = 1'b1;
			SLL:		reg_dst_c = 1'b1;
			SRL:		reg_dst_c = 1'b1;
			AND:		reg_dst_c = 1'b1;
			OR:			reg_dst_c = 1'b1;
			NOR:		reg_dst_c = 1'b1;
			XNOR:		reg_dst_c = 1'b1;
			IN:			reg_dst_c = 1'b1;
			OUT:		reg_dst_c = 1'b1;	// this value doesn't matter, 1 for consistency
		 // HALT:		
			MOV:		reg_dst_c = 1'b0;
			MOVI:		reg_dst_c = 1'b0;
			BA:			reg_dst_c = 1'b0;	// this value doesn't matter
			BE:			reg_dst_c = 1'b0;	// this value doesn't matter
			BNE:		reg_dst_c = 1'b0;	// this value doesn't matter
			BG:			reg_dst_c = 1'b0;	// this value doesn't matter
			BGE:		reg_dst_c = 1'b0;	// this value doesn't matter
			BL:			reg_dst_c = 1'b0;	// this value doesn't matter
			BLE:		reg_dst_c = 1'b0;	// this value doesn't matter
			default: 	reg_dst_c = 1'b0;
		endcase
	end
	
	// 2. RegWrite
	//    0: Register writes suppressed: store and branch instructions
	//    1: Register writes occur:		 R-type instructions, load, and move
	always_comb
	begin
		unique casez (instruction_data_i)
			LD:			reg_write_c = 1'b1;
			ST: 		reg_write_c = 1'b0;
			ADD:		reg_write_c = 1'b1;
			SUB:		reg_write_c = 1'b1;
			SLL:		reg_write_c = 1'b1;
			SRL:		reg_write_c = 1'b1;
			AND:		reg_write_c = 1'b1;
			OR:			reg_write_c = 1'b1;
			NOR:		reg_write_c = 1'b1;
			XNOR:		reg_write_c = 1'b1;
			IN:			reg_write_c = 1'b1;
			OUT:		reg_write_c = 1'b0;
		 // HALT:
			MOV:		reg_write_c = 1'b1;
			MOVI:		reg_write_c = 1'b1;
			BA: 		reg_write_c = 1'b0;
			BE: 		reg_write_c = 1'b0;
			BNE: 		reg_write_c = 1'b0;
			BG: 		reg_write_c = 1'b0;
			BGE: 		reg_write_c = 1'b0;
			BL: 		reg_write_c = 1'b0;
			BLE: 		reg_write_c = 1'b0;
			default: 	reg_write_c = 1'b0;
		endcase
	end
		
	// 3. ALUSrc
	//    0: 2nd ALU input is 2nd register
	//    1: 2nd ALU input is immediate
	always_comb
	begin
		unique casez (instruction_data_i)
			LD:			alu_src_c = 1'b0;
			ST: 		alu_src_c = 1'b0;
			MOVI:		alu_src_c = 1'b0;
			default: 	alu_src_c = 1'b1;
		endcase
	end
	
	// 4. MemWrite	
	//    0: Do not write data memory
	//    1: Perform write at address
	always_comb
	begin
		unique casez (instruction_data_i)
			ST: 		mem_write_c = 1'b1;
			default: 	mem_write_c = 1'b0;
		endcase
	end
		
	// 5. MemRead
	//    0: Do not read data memory
	//    1: Perform read at address
	always_comb
	begin
		unique casez (instruction_data_i)
			LD: 		mem_read_c = 1'b1;
			default: 	mem_read_c = 1'b0;
		endcase
	end
	
	// 6. MemToReg
	//    0: Present ALU result to register file for write
	//    1: Present ALU result to register file for write
	//    Note: it doesn't matter for store or branch instructions
	always_comb
	begin
		unique casez (instruction_data_i)
			LD: 		mem_to_reg_c = 1'b1;
			default: 	mem_to_reg_c = 1'b0;
		endcase
	end
	
	// 7. PCSrc
	//    0: PC = PC + 4
	//    1: PC = PC + 4 + immediate, branch
	// This value will be evaluated in adder_branch add_branch
 	
////////////////////////////////////////////////////////////////////////
// assign: control: next state logic 	

	// 0. sBootStrap= BS    = set everything to zero
	// 1. sFront	= IF	= instruction fetch
	// 2. sRegFetch	= ID	= instruction decode/register fetch
	// 3. sExecute	= EX	= execute/address calculation
	// 4. sMemRead	= MEM_R	= memory access read
	// 5. sWriteReg = WB_R  = write back to register file
	// 6. sWriteMem = WB_M  = write back to data memory
	// 7. sFinal    = FIN   = final state
	// 8. sHalt     = HALT  = HALT, THE END
	// 15.default	= ERROR

	typedef enum { sBootStrap, sFront, sRegFetch, sExecute, sMemRead, sWriteReg, sWriteMem, sFinal, sHalt, sBranchDelay } state_s;
	state_s substate_r, substate_n;
	
	always_ff @ (posedge clk)
		substate_r <= reset_i ? sFront: substate_n;
	
	always_comb
	begin
		unique case(substate_r)
		sBootStrap:
			begin
				substate_n = sFront;
			end
			
		sFront:
			begin
				substate_n = sRegFetch;
			end
			
		sRegFetch:
			begin
				unique casez (instruction_data_i)
					HALT:		substate_n = sHalt;
					default: 	substate_n = sExecute;
				endcase
			end
			
		sExecute:
			begin
				unique casez (instruction_data_i)
					LD: 		substate_n = sMemRead;
					ST:			substate_n = sWriteMem;
					ADD:		substate_n = sWriteReg;
					SUB:		substate_n = sWriteReg;
					SLL:		substate_n = sWriteReg;
					SRL:		substate_n = sWriteReg;
					AND:		substate_n = sWriteReg;
					OR:			substate_n = sWriteReg;
					NOR:		substate_n = sWriteReg;
					XNOR:		substate_n = sWriteReg;
					MOV:		substate_n = sWriteReg;
					MOVI:		substate_n = sWriteReg;
					BA:			substate_n = sBranchDelay;
					BE:			substate_n = sBranchDelay;
					BNE:		substate_n = sBranchDelay;
					BG:			substate_n = sBranchDelay;
					BGE:		substate_n = sBranchDelay;
					BL:			substate_n = sBranchDelay;
					BLE:		substate_n = sBranchDelay;
					CALL:		substate_n = sBranchDelay;
					RETURN:		substate_n = sBranchDelay;
					default: 	substate_n = sFinal;	// for branches			
				endcase			
			end	
			
		sMemRead:
			begin
				// LW
				substate_n = sWriteReg;
			end
		
		sWriteReg:
			begin
				substate_n = sFinal;
			end
		
		sWriteMem:
			begin
				// ST
				substate_n = sFinal;
			end
			
		sBranchDelay:
			begin
				substate_n = sFinal;
			end
			
		sFinal:
			begin
				substate_n = sFront;
			end
		
		sHalt:
			begin
				substate_n = sHalt;
			end
			
		default:
			begin
				substate_n = sFront;
			end
			
		endcase
		
	end

////////////////////////////////////////////////////////////////////////
// assign: control: enables
	
	// output from back end --> front end inputs
	// 1. restart_r
	// 2. restart_addr_o
	// 3. branch_o
	// 4. dequeue_o
	
	always_comb
	begin
	 	MemWrite		= 1'b0;
	 	MemRead			= 1'b0;
	  	RegWrite		= 1'b0;
		restart_r		= 1'b0;
		restart_addr_r	= 10'b0;
		start_r 		= 1'b0;
		done_r 			= 1'b0;
		branch_r 		= 1'b0;	
		dequeue_r    	= 1'b0;
		Level			= 1'b0;		
		
		
		unique case (substate_n)
			sBootStrap:
				begin
					restart_r = 1'b1;
					start_r = 1'b1;
				end
				
			sFront:
				begin			
					restart_r = 1'b0;	
																		
					dequeue_r = 1'b0;					
					branch_r = 1'b0;	
										
					start_r = 1'b0;
					done_r = 1'b0;				
				end
				
			//sRegFetch: 
			//	begin		
			//	end
				
			//sExecute:
			//	begin
			//	end
				
			sMemRead:
				begin
					MemRead = mem_read_c;
				end
				
			sWriteReg:
				begin
					RegWrite = reg_write_c;
				end
				
			sWriteMem: 
				begin
				  unique casez(instruction_data_i)
			       CALL:	   Level = Level + 1;
			       RETURN:	 Level = Level - 1;
		      endcase
		      
					MemWrite = mem_write_c;
					
				end
			
			sFinal:
				begin												
					branch_r = branch_true_r | return_true_r | call_true_r;	
					
					// if it's not a branch, dequeue
					// if it is a branch, don't dequeue
					if(~(branch_true_r | return_true_r | call_true_r))	
					begin
						dequeue_r = 1'b1;
					end		
					
					done_r = 1'b1;
										
				end
			
			//sHalt:
			//	begin
			//	end
				
		endcase
	end
		
	assign RegDst = reg_dst_c;
	assign ALUSrc = alu_src_c;
	assign MemToReg = mem_to_reg_c;
	
 	assign restart_o = restart_r;
 	assign restart_addr_o = 10'b0;
 	assign branch_o = branch_r;
 	assign dequeue_o = dequeue_r;	
 	assign store_data_o = reg7_r;	// %fp is in register 7


	
////////////////////////////////////////////////////////////////////////
// flip-flop

////////////////////////////////////////////////////////////////////////
// adder
adder_backend add_pc_next
(
	 .d0_i	(pc_r)
	,.d1_i	(34'b100)
	,.d_o 	(pc_next_r)
);

////////////////////////////////////////////////////////////////////////
// adder
compare_branch compare_branch
(
	 .isBranch_i	(instruction_data_i[16])
	,.control		(instruction_data_i[16:12])
	,.r1_i			(regfile_o_1)
	,.r2_i			(regfile_o_2)
	,.branch_true_o	(branch_true_r)
	,.return_true_o (return_true_r)
	,.call_true_o	(call_true_r)
);

////////////////////////////////////////////////////////////////////////
// shift-left-2
shiftleft shift_left
(
	 .d_i		(imm_r)
	,.d_shift 	(34'b10)
	,.d_o 		(offset_r)
);

////////////////////////////////////////////////////////////////////////
// sign extend
signext_backend	sign_ext
(
	 .d_i	(instruction_data_i[IMM_WIDTH-1:0])
	,.d_o 	(imm_r)
);

////////////////////////////////////////////////////////////////////////
// MUX: PCSrc: the mux to PCSrc

mux_backend mux_PCSrc
(
    .sel	(branch_true_r)
   ,.d0_i	(pc_next_r)
   ,.d1_i	(branch_pc_r)
   ,.d_o	(pc_r)
   
);

////////////////////////////////////////////////////////////////////////
// MUX: ALUSrc: the mux from regfile to ALU

mux_backend mux_ALUSrc
(
    .sel	(ALUSrc)
   ,.d0_i	(imm_r)
   ,.d1_i	(regfile_o_2)
   ,.d_o	(alu_input_2)
   
);

////////////////////////////////////////////////////////////////////////
// MUX: RegDst: the mux from input to regfile

mux_reg mux_RegDst
(
    .sel	(RegDst)
   ,.d0_i	(instruction_data_i[8:6])	// I-type register to store
   ,.d1_i	(instruction_data_i[5:3])	// R-type register to store
   ,.d_o	(reg_for_write)
   
);


////////////////////////////////////////////////////////////////////////
// MUX: MemToReg: the mux from dmem to regfile

mux_backend mux_MemToReg
(
    .sel	(MemToReg)
   ,.d0_i	(alu_o_r)
   ,.d1_i	(dmem_o_r)
   ,.d_o	(data_for_write)
   
);


////////////////////////////////////////////////////////////////////////
// register file
regfile reg_file
(
   .clk(clk)
  ,.wen_i(RegWrite)
  ,.wa_i(reg_for_write)
  ,.wd_i(data_for_write)
  ,.ra0_i(instruction_data_i[11:9])
  ,.ra1_i(instruction_data_i[8:6])
  
  ,.start_i(start_r)	// if(start) %fp = 0
  ,.done_i(done_r)		// if(done)  %fp = %fp - 1
  
  ,.branch_true_i(branch_true_r) // if(branch) %fp = fp - offset
  							// else %fp = %fp - 1
  ,.return_true_i(return_true_r) // if(return) %fp = %fp - 1
  ,.call_true_i(call_true_r)		// if(call) %fp = iaddr - offset
  ,.current_addr_i(instruction_addr_i)
  ,.offset_i(imm_r)	
  ,.level_i(Level)
	  
  ,.rd0_o(regfile_o_1)
  ,.rd1_o(regfile_o_2)
  ,.reg1 (reg1_r)
  ,.reg2 (reg2_r)
  ,.reg3 (reg3_r)
  ,.reg4 (reg4_r)
  ,.reg5 (reg5_r)
  ,.reg6 (reg6_r)
  ,.reg7 (reg7_r)
  ,.reg8 (reg8_r)  
  
);

////////////////////////////////////////////////////////////////////////
// data memory

dmem data_mem
(
   .reset_i(reset_i)
  ,.clk(clk)
  ,.read_en_i(MemRead)
  ,.write_en_i(MemWrite)
  ,.addr_i(alu_o_r[13:0])
  ,.din_i(regfile_o_2)
  ,.dout_o(dmem_o_r)
  
);

////////////////////////////////////////////////////////////////////////
// ALU

ALU alu
(
	.clk(clk)
   ,.d0_i(regfile_o_1)
   ,.d1_i(alu_input_2)
   ,.control(instruction_data_i[16:12])
   ,.d_o(alu_o_r)
);

////////////////////////////////////////////////////////////////////////
// assign: debug 	

 	assign reg1 = reg1_r;
 	assign reg2 = reg2_r;
 	assign reg3 = reg3_r;
 	assign reg4 = reg4_r;
 	assign reg5 = reg5_r;
 	assign reg6 = reg6_r;
 	assign reg7 = reg7_r;
 	assign reg8 = reg8_r; 
 	
 ////////////////////////////////////////////////////////////////////////
 

endmodule